﻿/*--------------------------------------------------------------------------------
 * Adaptive Intelligence - Foundation Framework Class Library
 * Version 2.00
 * 
 * Secure File Eraser Class
 *		Wipes and deletes an existing file on the disk.
 *------------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------------
 * System Imports
 *------------------------------------------------------------------------------*/
using System;
using System.IO;
using System.Windows.Forms;
using System.Threading;

namespace Adaptive.Foundation.IO.WipeDisk
{
    /// <summary>
    /// Wipes and deletes an existing file on the disk.
    /// </summary>
    /// <remarks>
    /// Used to erase a file securely in a multi-threaded fashion.
    /// </remarks>
    public sealed class SecureFileEraser : IDisposable
    {
        /*------------------------------------------------------------------------
         * Public Events
         *----------------------------------------------------------------------*/
        #region Public Events
        /// <summary>
        /// Occurs when the file erase operation begins.
        /// </summary>
        public event EventHandler EraseStart;
        /// <summary>
        /// Occurs when the file erase encounters an I/O failure.
        /// </summary>
        public event ExceptionEventHandler EraseFailure;
        /// <summary>
        /// Occurs when the file erase operation completes.
        /// </summary>
        public event EventHandler EraseComplete;
        /// <summary>
        /// Occurs as the file erase operation is processing.
        /// </summary>
        public event ProgressEventHandler Update;

        #endregion

        /*------------------------------------------------------------------------
		 * Private Member Declarations
		 *----------------------------------------------------------------------*/
        #region Private Member Declarations
        /// <summary>
        /// Path and name of the file to be erased.
        /// </summary>
        private string _FileName = string.Empty;
        /// <summary>
        /// File access object.
        /// </summary>
        private FileStream _File = null;
        /// <summary>
        /// Erasure execution thread.
        /// </summary>
        private Thread _EraseThread;
        /// <summary>
        /// Event-raising thread.
        /// </summary>
        private Thread _EventThread;
        /// <summary>
        /// Status.
        /// </summary>
        private string _Status = string.Empty;
        private int _Percent;
        /// <summary>
        /// 
        /// </summary>
        private Exception _Ex;
        #endregion

        /*------------------------------------------------------------------------
		 * Constructor / Dispose / Destructor Methods
		 *----------------------------------------------------------------------*/
        #region Constructor / Dispose / Destructor Methods
        /// <summary>
        /// Initializes a new instance of the class
        /// </summary>
        /// <remarks>
        /// This is the default constructor.
        /// </remarks>
        public SecureFileEraser(string fileName)
        {
            _FileName = fileName;
        }
        /// <summary>
        /// Deallocates internally allocated objects.
        /// </summary>
        public void Dispose()
        {
            _File = null;
            _FileName = null;
            _EraseThread = null;
            _EventThread = null;
            GC.SuppressFinalize(this);
        }
        #endregion

        /*------------------------------------------------------------------------
		 * Public Properties
		 *----------------------------------------------------------------------*/
        #region Public Properties
        /// <summary>
        /// Current file number.
        /// </summary>
        public int CurrentFile = 0;
        /// <summary>
        /// Total files to process.
        /// </summary>
        public int TotalFiles = 0;
        #endregion

        /*------------------------------------------------------------------------
		 * Public Methods / Functions
		 *----------------------------------------------------------------------*/
        #region Public Methods / Functions
        /// <summary>
        /// Begins the file-erasing process.
        /// </summary>
        public void BeginEraseFile()
        {
            if (_EraseThread == null)
            {
                _EraseThread = new Thread(new ThreadStart(RunEraseThread));
                _EraseThread.Name = "FileEraseThread-" + _FileName;
                _EraseThread.Priority = ThreadPriority.Normal;
                _EraseThread.Start();
            }
        }
        /// <summary>
        /// Waits for the file-erasing process to complete.
        /// </summary>
        public void EndEraseFile()
        {
            if (_EraseThread != null)
            {
                while (_EraseThread.IsAlive)
                    Application.DoEvents();
            }
            _EraseThread = null;
        }
        #endregion

        /*------------------------------------------------------------------------
		 * Event Methods / Functions
		 *----------------------------------------------------------------------*/
        #region Event Methods / Functions
        /// <summary>
        /// Raises the <see cref="EraseStart"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="EventArgs"/> event arguments.
        /// </param>
        private void OnEraseStart(EventArgs e)
        {
            if (EraseStart != null)
                EraseStart(this, e);
        }
        /// <summary>
        /// Raises the <see cref="EraseComplete"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="EventArgs"/> event arguments.
        /// </param>
        private void OnEraseComplete(EventArgs e)
        {
            if (EraseComplete != null)
                EraseComplete(this, e);
        }
        /// <summary>
        /// Raises the <see cref="EraseFailure"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="ExceptionEventArgs"/> event arguments.
        /// </param>
        private void OnEraseFailure(ExceptionEventArgs e)
        {
            if (EraseFailure != null)
                EraseFailure(this, e);
        }
        /// <summary>
        /// Raises the <see cref="Update"/> event.
        /// </summary>
        /// <param name="e">
        /// A <see cref="ProgressEventArgs"/> event arguments.
        /// </param>
        private void OnUpdate(ProgressEventArgs e)
        {
            if (Update != null)
                Update(this, e);
        }
        #endregion

        /*------------------------------------------------------------------------
		 * Private Methods / Functions
		 *----------------------------------------------------------------------*/
        #region Private Methods / Functions
        /// <summary>
        /// Attempts to open the file for writing.
        /// </summary>
        private void OpenFileForWriting()
        {
            try
            {
                _File = new FileStream(_FileName, FileMode.Open, FileAccess.Write);
            }
            catch (IOException ex)
            {
                _Ex = ex;
                LaunchExceptionEvent(ex);
                _File = null;
            }
        }
        /// <summary>
        /// Overwrites the existing file data.
        /// </summary>
        private void OverwriteContent(int currentIteration)
        {
            Random rnd;
            BinaryWriter writer = null;
            int length = 0;
            int count = 0;
            byte[] overWriteContent;
            int fileLength = 0;

            if (_File != null)
            {
                try
                {
                    rnd = new Random(System.Environment.TickCount);
                    writer = new BinaryWriter(_File);
                    fileLength = (int)_File.Length;

                    //Get the data to overwrite with.
                    overWriteContent = GetDataContentForIteration(currentIteration);
                    length = 65536;

                    //Perform overwrite.
                    do
                    {
                        writer.Write(overWriteContent);
                        writer.Flush();
                        _File.Seek((-1 * length), SeekOrigin.Current);
                        writer.Write(overWriteContent);
                        writer.Flush();
                        _File.Seek((-1 * length), SeekOrigin.Current);
                        writer.Write(overWriteContent);
                        writer.Flush();
                        _File.Seek((-1 * length), SeekOrigin.Current);


                        count += length;
                    } while (count <= fileLength);
                    writer.Flush();
                }
                catch (IOException ex)
                {
                    LaunchExceptionEvent(ex);
                }
            }

            overWriteContent = null;
            rnd = null;
            writer = null;
        }

        /// <summary>
        /// Closes the file.
        /// </summary>
        private void CloseFile()
        {
            if (_File != null)
            {
                try
                {
                    _File.Close();
                    _File = null;
                }
                catch
                { }
            }
        }

        /// <summary>
        /// Gets the data bytes to use when overwriting the file based on the current
        /// iteration.
        /// </summary>
        /// <param name="iteration"></param>
        /// <returns></returns>
        private byte[] GetDataContentForIteration(int iteration)
        {
            byte[] returnValue = null;
            byte[] itemValue = null;
            int count = 0;

            switch (iteration)
            {
                case 0:
                    itemValue = new byte[3] { 0, 0, 0 };
                    break;
                case 1:
                    itemValue = new byte[3] { 255, 255, 255 };
                    break;
                case 2:
                    itemValue = new byte[3] { 170, 170, 170 };
                    break;
                case 3:
                    itemValue = new byte[3] { 85, 85, 85 };
                    break;
                case 4:
                    itemValue = new byte[3] { 240, 240, 240 };
                    break;
                case 5:
                    itemValue = new byte[3] { 15, 15, 15 };
                    break;
                case 6:
                    itemValue = new byte[3] { 0x55, 0x55, 0x55 };
                    break;
                case 7:
                    itemValue = new byte[3] { 0xAA, 0xAA, 0xAA };
                    break;

                case 8:
                    itemValue = new byte[3] { 0x92, 0x49, 0x24 };
                    break;

                case 9:
                    itemValue = new byte[3] { 0x49, 0x24, 0x92 };
                    break;

                case 10:
                    itemValue = new byte[3] { 0x24, 0x92, 0x49 };
                    break;

                case 11:
                    itemValue = new byte[3] { 0x00, 0x00, 0x00 };
                    break;
                case 12:
                    itemValue = new byte[3] { 0x11, 0x11, 0x11 };
                    break;
                case 13:
                    itemValue = new byte[3] { 0x22, 0x22, 0x22 };
                    break;
                case 14:
                    itemValue = new byte[3] { 0x33, 0x33, 0x33 };
                    break;
                case 15:
                    itemValue = new byte[3] { 0x44, 0x44, 0x44 };
                    break;
                case 16:
                    itemValue = new byte[3] { 0x55, 0x55, 0x55 };
                    break;
                case 17:
                    itemValue = new byte[3] { 0x66, 0x66, 0x66 };
                    break;
                case 18:
                    itemValue = new byte[3] { 0x77, 0x77, 0x77 };
                    break;
                case 19:
                    itemValue = new byte[3] { 0x88, 0x88, 0x88 };
                    break;
                case 20:
                    itemValue = new byte[3] { 0x99, 0x99, 0x99 };
                    break;
                case 21:
                    itemValue = new byte[3] { 0xAA, 0xAA, 0xAA };
                    break;
                case 22:
                    itemValue = new byte[3] { 0xBB, 0xBB, 0xBB };
                    break;
                case 23:
                    itemValue = new byte[3] { 0xCC, 0xCC, 0xCC };
                    break;
                case 24:
                    itemValue = new byte[3] { 0xDD, 0xDD, 0xDD };
                    break;
                case 25:
                    itemValue = new byte[3] { 0xEE, 0xEE, 0xEE };
                    break;
                case 26:
                    itemValue = new byte[3] { 0xFF, 0xFF, 0xFF };
                    break;
                case 27:
                    itemValue = new byte[3] { 0x92, 0x49, 0x24 };
                    break;
                case 28:
                    itemValue = new byte[3] { 0x49, 0x24, 0x92 };
                    break;
                case 29:
                    itemValue = new byte[3] { 0x24, 0x92, 0x49 };
                    break;
                case 30:
                    itemValue = new byte[3] { 0x6D, 0xB6, 0xDB };
                    break;
                case 31:
                    itemValue = new byte[3] { 0xB6, 0xDB, 0x6D };
                    break;
                case 32:
                    itemValue = new byte[3] { 0xDB, 0x6D, 0xB6 };
                    break;
                case 33:
                    itemValue = new byte[3] { 15, 15, 15 };
                    break;
                case 34:
                    itemValue = new byte[3] { 240, 240, 240 };
                    break;
                case 35:
                    itemValue = new byte[3] { 85, 85, 85 };
                    break;
                case 36:
                    itemValue = new byte[3] { 170, 170, 170 };
                    break;
                case 37:
                    itemValue = new byte[3] { 255, 255, 255 };
                    break;
                case 38:
                    itemValue = new byte[3] { 0, 0, 0 };
                    break;
            }

            if (itemValue != null)
            {
                returnValue = new byte[65536];
                for (count = 0; count < 21845; count += 3)
                    Array.Copy(itemValue, 0, returnValue, count, 3);
            }

            return returnValue;
        }

        /// <summary>
        /// Deletes the file.
        /// </summary>
        private void DeleteFile()
        {
            try
            {
                File.Delete(_FileName);
            }
            catch
            { }
        }
        /// <summary>
        /// Re-creates the file with false data.
        /// </summary>
        private void CreateNewFile()
        {
            Random rnd = null;
            int count = 0;
            int length = 0;
            byte byteValue = 0;

            rnd = new Random(System.Environment.TickCount);
            try
            {
                _File = new FileStream(_FileName, FileMode.Open, FileAccess.Write);
                length = (int)_File.Length;

                for (count = 0; count < length; count++)
                {
                    byteValue = (byte)rnd.Next(255);
                    _File.WriteByte(byteValue);
                }

                _File.Close();
            }
            catch
            { }
            rnd = null;
        }
        #endregion

        /*------------------------------------------------------------------------
		 * Thread Methods
		 *----------------------------------------------------------------------*/
        #region Thread Methods
        /// <summary>
        /// Thread to perform the execution.
        /// </summary>
        private void RunEraseThread()
        {
            int count = 0;

            //Update the UI.
            StartEvent();

            for (count = 0; count < 39; count++)
            {
                //Open file.
                OpenFileForWriting();

                //Overwrite data.
                OverwriteContent(count);

                //Close file.
                CloseFile();
                Application.DoEvents();
                UpdateUi("Erasing Temporary Files (" + CurrentFile.ToString() +
                    " of " + TotalFiles.ToString() + ")",
                    (int)(((float)count / 40) * 100));
            }

            //Delete the file once, twice, three times...
            CreateNewFile();
            CreateNewFile();
            CreateNewFile();

            for (count = 37; count < 39; count++)
            {
                //Open file.
                OpenFileForWriting();

                //Overwrite data.
                OverwriteContent(count);

                //Close file.
                CloseFile();
            }

            DeleteFile();
            //Update the UI.
            CompleteEvent();

        }
        /// <summary>
        /// Launches an event thread.
        /// </summary>
        /// <param name="threadMethod"></param>
        private void LaunchEventThread(ThreadStart threadMethod)
        {
            _EventThread = new Thread(threadMethod);
            _EventThread.Start();
        }
        /// <summary>
        /// Starts the start event.
        /// </summary>
        private void StartEvent()
        {
            LaunchEventThread(new ThreadStart(RunStartEvent));
        }
        /// <summary>
        /// Starts the complete event.
        /// </summary>
        private void CompleteEvent()
        {
            LaunchEventThread(new ThreadStart(RunCompleteEvent));
        }
        /// <summary>
        /// Thread to execute the exception events.
        /// </summary>
        /// <param name="ex"></param>
        private void LaunchExceptionEvent(Exception ex)
        {
            _Ex = ex;
            LaunchEventThread(new ThreadStart(RunExceptionEventThread));
        }
        /// <summary>
        /// Runs the update event.
        /// </summary>
        /// <param name="status"></param>
        /// <param name="percentDone"></param>
        private void UpdateUi(string status, int percentDone)
        {
            _Status = status;
            _Percent = percentDone;
            LaunchEventThread(new ThreadStart(RunUpdateEvent));
        }
        /// <summary>
        /// Thread to execute the exception event.
        /// </summary>
        private void RunExceptionEventThread()
        {
            ExceptionEventArgs newArgs = null;

            newArgs = new ExceptionEventArgs();
            newArgs.ExceptionInstance = _Ex;
            OnEraseFailure(newArgs);
            newArgs = null;
        }
        /// <summary>
        /// Thread to execute the update event.
        /// </summary>
        private void RunUpdateEvent()
        {
            ProgressEventArgs newArgs = null;

            newArgs = new ProgressEventArgs(_Status, _Percent);
            OnUpdate(newArgs);
            newArgs = null;
        }
        /// <summary>
        /// Thread to execute the start event.
        /// </summary>
        private void RunStartEvent()
        {
            OnEraseStart(EventArgs.Empty);
        }
        /// <summary>
        /// Thread to execute the completion event.
        /// </summary>
        private void RunCompleteEvent()
        {
            OnEraseStart(EventArgs.Empty);
        }
        #endregion
    }
}
